import type { PDFPageProxy, PageViewport, RenderTask } from 'pdfjs-dist'
import { throttle } from 'lodash-es'
import { reactive, nextTick } from 'vue'
import * as PDF from 'pdfjs-dist/legacy/build/pdf.mjs'
import { TextLayerBuilder } from 'pdfjs-dist/web/pdf_viewer.mjs'
import workerSrc from 'pdfjs-dist/legacy/build/pdf.worker.min.mjs?url'

PDF.GlobalWorkerOptions.workerSrc = workerSrc

interface PdfRendererState {
  // 当前缩放
  pdfScale: number;
  // 当前位置
  scrollTop: number;
  // 单面高度
  pageHeight: number;
  // 总页高度
  totalHeight: number;
  // 分页总数
  totalPages: number;
  // 当前页码
  currentPage: number;
  // 显示页码列表
  visiblePages: number[];
  // 是否锁定页面
  isPageLocked: boolean;
  // 是否正在加载
  isLoading: boolean;
  // 是否出现错误
  isError: boolean;
  // 文本搜索条件
  searchQuery: { keyword: string; page_no: number };
}

class TextHighlighter {
    private readonly keyword: string
    private readonly pageNo: number
    private readonly currentPageIndex: number

    constructor(
        item: { keyword: string; page_no: number },
        currentPageIndex: number
    ) {
        this.keyword = item.keyword.replace(/\s+/g, '')
        this.pageNo = item.page_no
        this.currentPageIndex = currentPageIndex
    }

    // 设置文本映射并高亮关键字
    setTextMapping(textDivs: HTMLElement[], textContentItemsStr: string[]) {
        setTimeout(() => {
            if (this.currentPageIndex === this.pageNo) {
                const fullText = textContentItemsStr.join('').replace(/\s+/g, '')
                const keywordIndex = fullText.indexOf(this.keyword)
                if (keywordIndex === -1) {
                    return
                }
                let currentIndex = 0
                let remainingKeyword = this.keyword
                textDivs.forEach((div, index) => {
                    const text = textContentItemsStr[index].replace(/\s+/g, '')
                    const textLength = text.length
                    if (remainingKeyword.length > 0) {
                        const start = Math.max(0, keywordIndex - currentIndex)
                        const end = Math.min(start + remainingKeyword.length, textLength)
                        if (start < textLength) {
                            div.innerHTML =
                textContentItemsStr[index].substring(0, start) +
                `<b class="highlight">${textContentItemsStr[index].substring(
                    start,
                    end
                )}</b>` +
                textContentItemsStr[index].substring(end)
                            remainingKeyword = remainingKeyword.substring(end - start)
                        }
                    }
                    currentIndex += textLength
                })
            }
        }, 100)
    }

    enable(): void {
        document.querySelectorAll('.highlight').forEach((el: HTMLElement | any) => {
            el.style.opacity = '0.5'
            el.style.color = 'transparent'
        })
    }

    disable(): void {
        document.querySelectorAll('.highlight').forEach((el: HTMLElement | any) => {
            el.style.backgroundColor = ''
            el.style.color = ''
        })
    }
}

export function usePdfRenderer(visiblePageCount = 7) {
    // 必须使用非响应式存储,否则报错: Cannot read from private field
    let pdfDocument: any = ''
    const renderingTasks = new Map<number, RenderTask>()
    const renderedPages = new Map<number, HTMLCanvasElement>()
    const statePdf = reactive<PdfRendererState>({
        pdfScale: 100,
        scrollTop: 0,
        pageHeight: 0,
        totalHeight: 0,
        totalPages: 1,
        currentPage: 1,
        visiblePages: [],
        isPageLocked: false,
        isLoading: true,
        isError: false,
        searchQuery: { keyword: '', page_no: 0 },
    })

    /**
   * 初始化PDF
   */
    const initializePdf = async (path: string) => {
        try {
            // 取消旧渲染任务
            renderingTasks.forEach((task) => task.cancel())
            renderingTasks.clear()

            // 清空旧PDF文档
            pdfDocument = null
            renderedPages.clear()

            // 初始化新的PDF
            statePdf.scrollTop = 0
            statePdf.isLoading = true
            statePdf.currentPage = 1
            statePdf.visiblePages = []
            const loadingTask = PDF.getDocument({
                url: path,
                cMapUrl: 'https://cdn.jsdelivr.net/npm/pdfjs-dist@4.8.69/cmaps/',
                cMapPacked: true,
            })
            pdfDocument = await loadingTask.promise
            statePdf.totalPages = pdfDocument.numPages
            await updatePageMetrics()
        } catch (e) {
            statePdf.isError = true
            // eslint-disable-next-line no-console
            console.log('获取pdf失败---->', e)
        } finally {
            statePdf.isLoading = false
        }
    }

    /**
   * 更新页面尺寸
   */
    const updatePageMetrics = async () => {
        if (!pdfDocument) {
            return
        }
        const firstPage = await pdfDocument.getPage(1)
        const viewport = getViewport(firstPage, statePdf.pdfScale)
        statePdf.pageHeight = viewport.height
        statePdf.totalHeight = statePdf.pageHeight * statePdf.totalPages
        updateVisiblePages()
    }

    /**
   * 获取页面视图
   *
   * @param {PDFPageProxy} page
   * @param {Number} scale
   * @returns {PageViewport}
   */
    const getViewport = (page: PDFPageProxy, scale: number): PageViewport => {
        const containerWidth: number = getContainerWidth()
        return page.getViewport({
            scale: containerWidth / page.view[2] * (scale / 100),
        })
    }

    /**
   * 获取容器宽度
   */
    const getContainerWidth = () => {
        return document.querySelector('.pdf-container')?.clientWidth || 0
    }

    /**
   * 更新可见页面
   *
   * @param {Number} scrollTop
   */
    const updateVisiblePages = (scrollTop: number = 0) => {
        const middlePageOffset: number = Math.floor(visiblePageCount / 2)
        let currentPage: number =
      Math.floor((scrollTop + 1) / statePdf.pageHeight) + 1
        currentPage = Math.max(1, Math.min(statePdf.totalPages, currentPage))

        const firstVisiblePage: number = Math.max(
            1,
            currentPage - middlePageOffset
        )
        const lastVisiblePage: number = Math.min(
            statePdf.totalPages,
            firstVisiblePage + visiblePageCount - 1
        )

        if (!statePdf.isPageLocked) {
            statePdf.currentPage = currentPage
        }

        statePdf.visiblePages = Array.from(
            { length: lastVisiblePage - firstVisiblePage + 1 },
            (_, i) => i + firstVisiblePage
        )

        cleanUpOffscreenPages()
        renderVisiblePages()
    }

    /**
   * 清空离屏页面
   */
    const cleanUpOffscreenPages = () => {
        renderedPages.forEach((_: HTMLCanvasElement, pageNum: number) => {
            if (!statePdf.visiblePages.includes(pageNum)) {
                renderedPages.delete(pageNum)
            }
        })
    }

    /**
   * 渲染可见页面
   */
    const renderVisiblePages = () => {
        statePdf.visiblePages.forEach((pageNum: number) => {
            requestAnimationFrame(() => renderPage(pageNum))
        })
    }

    /**
   * 渲染页面
   *
   * @param pageNum
   * @returns
   */
    const renderPage = async (pageNum: number) => {
        if (!pdfDocument || renderedPages.has(pageNum)) {
            return
        }

        // 取消旧渲染任务
        if (renderingTasks.has(pageNum)) {
            renderingTasks.get(pageNum)?.cancel()
        }

        // 渲染页面
        const page = await pdfDocument.getPage(pageNum)
        await nextTick()

        const canvas: HTMLCanvasElement | null = getPageCanvas(pageNum)
        if (!canvas) {
            return
        }

        const ctx: CanvasRenderingContext2D | null = canvas.getContext('2d')
        if (!ctx) {
            return
        }

        const viewport = getViewport(page, statePdf.pdfScale)
        setCanvasDimensions(canvas, viewport)
        ctx.clearRect(0, 0, canvas.width, canvas.height)

        try {
            const renderTask = page.render({ canvasContext: ctx, viewport })
            renderingTasks.set(pageNum, renderTask)
            await renderTask.promise
            await renderTextLayer(page, viewport, canvas, pageNum)
        } catch (error: any) {
            if (!error.message.startsWith('Rendering cancelled')) {
                console.error(error)
            }
        } finally {
            renderingTasks.delete(pageNum)
            renderedPages.set(pageNum, canvas)
        }
    }

    /**
   * 设置画布尺寸
   *
   * @param {HTMLCanvasElement} canvas
   * @param {PageViewport} viewport
   */
    const setCanvasDimensions = (
        canvas: HTMLCanvasElement,
        viewport: PageViewport
    ): void => {
        canvas.height = viewport.height
        canvas.width = viewport.width
    }

    /**
   * 获取页面画布
   *
   * @param pageNum
   * @returns {HTMLElement | null}
   */
    const getPageCanvas = (pageNum: number): HTMLCanvasElement | null => {
        return document.getElementById(`page-${pageNum}`) as HTMLCanvasElement
    }

    /**
   * 渲染文本层
   */
    const renderTextLayer = async (
        page: PDFPageProxy,
        viewport: PageViewport,
        _canvas: HTMLCanvasElement,
        pageNum: number
    ): Promise<void> => {
        // 创建高亮器
        const highlighter: any = new TextHighlighter(statePdf.searchQuery, pageNum)
        await nextTick()

        // 创建 TextLayerBuilder 实例
        const textLayerBuilder = new TextLayerBuilder({
            // 当前页面
            pdfPage: page,
            // 文本高亮 [可选]
            highlighter: statePdf.searchQuery.keyword.length
                ? (highlighter as any)
                : null,
        })

        const textLayerContainer: HTMLElement | null = document.getElementById(
            `text-layer-${pageNum}`
        )
        const canvas: HTMLCanvasElement | null = getPageCanvas(pageNum)

        if (textLayerContainer) {
            textLayerContainer.innerHTML = ''
            textLayerContainer.style.setProperty(
                '--scale-factor',
                viewport.scale.toString()
            )
            textLayerContainer.style.width = canvas?.width + 'px' || 0 + 'px'
            textLayerContainer.style.height = canvas?.height + 'px' || 0 + 'px'

            textLayerContainer.appendChild(textLayerBuilder.div)
        }

        // 渲染文本层
        await textLayerBuilder.render({
            viewport: viewport
        })

        // 启用高亮
        if (statePdf.searchQuery.keyword.length) {
            highlighter.enable()
        }
    }

    /**
   * 重新渲染页面比例
   */
    const reRenderScale = async (): Promise<void> => {
        const relativeScroll = statePdf.scrollTop / statePdf.totalHeight

        renderedPages.clear()
        await updatePageMetrics()

        statePdf.scrollTop = relativeScroll * statePdf.totalHeight
        scrollToPosition(statePdf.scrollTop)

        renderVisiblePages()
    }

    /**
   * 滚动条滚到指定页码
   */
    const scrollToPage = (page: number): void => {
        const scrollbar = document.getElementById(`page-container-${page}`)
        if (scrollbar) {
            scrollbar.scrollIntoView({
                block: 'start',
                behavior: 'smooth',
            })
        }
    }

    /**
   * 跳转滚动条指定位置
   */
    const scrollToPosition = (scrollTop: number): void => {
        const scrollbar: HTMLElement = document.querySelector(
            '#scrollbar .el-scrollbar__wrap'
        ) as HTMLElement
        if (scrollbar) {
            scrollbar.scrollTop = scrollTop
        }
    }

    /**
   * 监听滚动的事件【对外】
   */
    const listenScroll = throttle(({ scrollTop }: { scrollTop: number }) => {
        statePdf.scrollTop = scrollTop
        updateVisiblePages(scrollTop)
    }, 100)

    /**
   * PDF样式
   * @param pageNum
   */
    const pageStylesObj: any = (pageNum: number) => {
        const page: number = pageNum - 1
        return {
            position: 'absolute',
            top: page * statePdf.pageHeight + page * 10 + 'px',
            left: '0',
            right: '0',
            display: 'flex',
            justifyContent: 'center',
        }
    }

    /**
   * 跳转到指定页码【对外】
   */
    const pageJumpNumber = async (direction: 'next' | 'prev' | number) => {
        if (statePdf.isPageLocked) {
            return
        }

        statePdf.isPageLocked = true
        let targetPage: number = statePdf.currentPage

        if (direction === 'prev' && targetPage > 1) {
            targetPage -= 1
        } else if (direction === 'next' && targetPage < statePdf.totalPages) {
            targetPage += 1
        } else if (typeof direction === 'number') {
            targetPage = Math.max(1, Math.min(statePdf.totalPages, direction))
            const middlePageOffset = Math.floor(visiblePageCount / 2)
            const firstVisiblePage = Math.max(1, targetPage - middlePageOffset)
            const lastVisiblePage = Math.min(statePdf.totalPages, firstVisiblePage + visiblePageCount - 1)

            statePdf.visiblePages = Array.from(
                { length: lastVisiblePage - firstVisiblePage + 1 },
                (_, i) => i + firstVisiblePage
            )
        }

        await nextTick()

        scrollToPage(targetPage)
        statePdf.isPageLocked = false
    }

    /**
   * 页面缩放比例更新【对外】
   */
    const pageScaleUpdate = async (direction: 'next' | 'prev' | number): Promise<void> => {
        if (direction === 'next' || direction === 'prev') {
            const scales: number[] = [50, 75, 100, 125, 150, 175, 200]
            const currentIndex: number = scales.indexOf(statePdf.pdfScale)

            let newScale: number = statePdf.pdfScale

            if (direction === 'next' && currentIndex < scales.length - 1) {
                newScale = scales[currentIndex + 1]
            } else if (direction === 'prev' && currentIndex > 0) {
                newScale = scales[currentIndex - 1]
            }

            if (newScale !== statePdf.pdfScale) {
                statePdf.pdfScale = newScale
                await reRenderScale()
            }
        } else {
            await reRenderScale()
        }
    }

    /**
   * 页面文本字符搜索【对外】
   */
    const pageTextSearch = async (query: {
    keyword: string;
    page_no: number;
  }) => {
    // 更新搜索条件
        statePdf.searchQuery = query

        // 跳转到目标页码
        await pageJumpNumber(query.page_no)

        // 清除目标页码缓存
        renderedPages.delete(query.page_no)

        // 重新渲染目标页码
        await renderPage(query.page_no)
    }

    return {
    // 状态数据
        statePdf,
        // 页面滚动监听
        listenScroll,
        // 页面渲染样式
        pageStylesObj,
        // 初始化PDF页
        initializePdf,
        // 页面页码跳转
        pageJumpNumber,
        // 页面比例缩放
        pageScaleUpdate,
        // 页面文本搜索
        pageTextSearch,
    }
}
